package cn.haoran.modules.palaeobios.service.impl;

import cn.haoran.common.exception.AppException;

import cn.haoran.common.utils.ObjectConvertUtils;
import cn.haoran.common.utils.PageUtils;
import cn.haoran.common.utils.Query;

import cn.haoran.common.utils.RedisUtils;
import cn.haoran.modules.palaeobios.entity.Article;
import cn.haoran.modules.palaeobios.mapper.ArticleMapper;
import cn.haoran.modules.palaeobios.service.ArticleService;
import cn.haoran.modules.palaeobios.state.EsConfig;
import cn.haoran.modules.palaeobios.utils.BloomFilterUtils;
import cn.haoran.modules.palaeobios.utils.Utils;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;

import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;

import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

import java.io.IOException;


import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static javax.swing.plaf.synth.ColorType.MAX_COUNT;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author haoran
 * @since 2019-12-12
 */
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
    @Resource
    private RestHighLevelClient client;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private BloomFilterUtils bloomFilterUtils;
    private static final Logger LOGGER = LoggerFactory.getLogger("文章查找相关:");

    @Override
    public List<Long> getAllId() {
        return this.baseMapper.findAllId();
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        /* 判断是否为管理员后台接口 */
        boolean isNotAdmin = true;// 不是管理员访问的话 不会显示草稿文章内容
        String admin = (String) params.get("admin");
        if ("1".equals(admin)){
            isNotAdmin = false;
        }
        String name = (String) params.get("title");
        List<String> dateList = new ArrayList<>();
        dateList.add("create_date");
        Long columnId = Long.parseLong(params.get("columnId").toString());
        Page<Article> page = this.selectPage(
                new Query<Article>(params).getPage(),
                new EntityWrapper<Article>().eq("column_id",columnId).orderDesc(dateList).eq(isNotAdmin,"type",2).like(StringUtils.isNotBlank(name),"title",name));
        return new PageUtils(page);
    }

    @Override
    public Article getArticleById(Long id) {
        Map<Object, Object> map = redisUtils.hmget("article:"+ id);
        if (map.size() == 0){
            synchronized (this){
                map = redisUtils.hmget("article:"+ id);
                if (map.size() == 0 && bloomFilterUtils.hit(id)) {
                    Article article = this.baseMapper.selectById(id);
                    objectToMap(article);
                    return article;
                }else if (!bloomFilterUtils.hit(id)){
                    /* 布隆过滤器无文章数据时候 返回提示 */
                    Article article = new Article();
                    article.setCreateDate(LocalDateTime.now());
                    article.setTitle("抱歉你访问的文章不存在");
                    article.setColumnId(2L);
                    article.setContent("请访问正确的文章路径或你浏览的文章已删除,具体信息请联系管理员");
                    return article;
                }

            }
        }
        Article article = new Article();
        article.setId(Long.parseLong(map.get("id").toString()));
        article.setColumnId(Long.parseLong(map.get("columnId").toString()));
        article.setType(Integer.parseInt(map.get("type").toString()));
        article.setTitle(map.get("title").toString());
        Instant instant = Instant.ofEpochMilli(Long.parseLong(map.get("createDate").toString()));
        ZoneId zone = ZoneId.systemDefault();
        article.setCreateDate(LocalDateTime.ofInstant(instant, zone));
        article.setContent(map.get("content").toString());

        return article;
    }

    @Override
    @Transactional
    public void updateArticleById(Article article) {
        this.baseMapper.updateById(article);
        redisUtils.del("article:"+article.getId());
        try {
            deleteArticleById(article.getId().toString());
            addEsArticle(article);
        }catch (Exception e){
            throw new AppException("搜索引擎未知异常!");
        }
    }

    @Override
    public void deleteArticle(Long[] ids)  {
        List<Long> list = Arrays.asList(ids);
        this.baseMapper.deleteBatchIds(list);
        for (Long i : list){
            try {
                deleteArticleById(i.toString());
                redisUtils.del("article:"+i);
            } catch (IOException e) {
               throw new AppException("搜索引擎未知异常");
            }
        }
    }

    @Override
    public void addArticle(Article article) {
        //将id放置到布隆过滤器
        bloomFilterUtils.addData(article.getId());
        try {
            addEsArticle(article);
        }catch (Exception e){
            throw new AppException("搜索引擎未知异常");
        }
    }

    @Override
    public void synchronizationArticle() {
            try {
                /*deleteAllArticle();*/
                importAll();
            }catch (Exception e){
                throw new AppException(e.getMessage());
            }
    }

    @Override
    public List<Article> searchArticleByContent(Map<String, Object> map) {
        /* es深分页 */



        // 创建查询请求
        SearchRequest searchRequest = new SearchRequest(EsConfig.index);
        // 创建查询构造
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /* es多条件搜索  详情请：https://blog.csdn.net/Leige_Smart/article/details/80901059 */
        MatchQueryBuilder m1 = QueryBuilders.matchQuery("title",map.get("name"));
        MatchQueryBuilder m2 = QueryBuilders.matchQuery("content",map.get("name"));
        QueryBuilder qb2 = QueryBuilders.boolQuery()
                .should(m1)
                .should(m2);
        searchSourceBuilder.query(qb2);

        //设置高亮
        String preTags = "<font color=\"red\">";
        String postTags = "</font>";
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(preTags);
        highlightBuilder.postTags(postTags);
        //设置高亮字段
        highlightBuilder.field("content");
        highlightBuilder.field("title");
        //设置高亮信息
        searchSourceBuilder.highlighter(highlightBuilder);
        // TODO  分页
//        searchSourceBuilder.from(0);
//        searchSourceBuilder.size(100);
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = null;
        try {
            response = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
           throw new AppException("搜索遇到未知错误");
        }
        SearchHit[] hits = response.getHits().getHits();
        List<Article> articleList = new LinkedList<>();
        for(SearchHit hit: hits){
            Article article = JSONObject.parseObject(hit.getSourceAsString(),Article.class);
            Map<String, HighlightField> hitMap = hit.getHighlightFields();
            HighlightField highlightField = hitMap.get("content");
            HighlightField highlightTitle = hitMap.get("title");
            if (highlightField != null) {
                Text[] text = highlightField.getFragments();
                article.setContent(text[0].toString());
            }else{
                article.setContent("...");
                Text[] text = highlightTitle.getFragments();
                article.setTitle(text[0].toString());
            }
            articleList.add(article);
        }
        return articleList;
    }


    /* redis相关 */
    private void objectToMap(Article article) {
        Map<String,Object> map = new HashMap<>();
        map.put("id",article.getId());
        map.put("columnId",article.getColumnId());
        map.put("title",article.getTitle());
        map.put("content",article.getContent());
        map.put("titleImgId",article.getTitleImgId());
        map.put("titleImgUrl",article.getTitleImgUrl());
        map.put("type",article.getType());
        ZoneId zone = ZoneId.systemDefault();
        Instant instant = article.getCreateDate().atZone(zone).toInstant();
        map.put("createDate",instant.toEpochMilli());
        redisUtils.hmset("article:"+article.getId(),map,60*2);
    }

    /* es 相关*/
    /* 删除全部es中的文章数据  */
    private void deleteAllArticle() throws IOException {
        DeleteByQueryRequest request = new DeleteByQueryRequest(EsConfig.index);
        BulkByScrollResponse response = client.deleteByQuery(request,RequestOptions.DEFAULT);
    }
    /* 插入es一条文章 */
    private void addEsArticle(Article article) throws IOException {
        IndexRequest request = new IndexRequest(EsConfig.index).id(article.getId().toString()).source(Utils.beanToMap(article));
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    }
    /* 修改一条文章 */
    private void updateArticleById(Article article,String id) throws IOException {
        org.elasticsearch.action.update.UpdateRequest request = new UpdateRequest(EsConfig.index,id).doc(Utils.beanToMap(article));
        UpdateResponse response = client.update(request,RequestOptions.DEFAULT);
    }
    /* 删除一条文章 */
    private void deleteArticleById(String id) throws IOException {
        DeleteRequest request = new DeleteRequest(EsConfig.index,id);
        client.delete(request,RequestOptions.DEFAULT);
    }
    /* 从数据库中查询数据后 批量导入到es中 */
    private void importAll() throws IOException {
        List<Article> list = this.baseMapper.selectList(null);
        for (Article article : list){
            article.setCreateDate(null);
            addEsArticle(article);
        }
    }
}
